package eva

import (
	"testing"

	"gitcode.com/deyiyangyang/gascheme/lex"
	"gitcode.com/deyiyangyang/gascheme/obj"
	"gitcode.com/deyiyangyang/gascheme/par"
)

// Helper function to test evaluation of expressions
func testEval(input string) obj.Object {
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		panic(err)
	}

	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)
	return Eval(expr, env)
}

// TestSqrtFunction tests the sqrt function with various inputs
func TestSqrtFunction(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{"(sqrt 4)", "2"},
		{"(sqrt 9)", "3"},
		{"(sqrt 2)", "1.4142135623730951"},
		{"(sqrt 0)", "0"},
		{"(sqrt 1)", "1"},
		{"(sqrt 16)", "4"},
		{"(sqrt 25)", "5"},
		{"(sqrt 0.25)", "0.5"},
		{"(sqrt 1/4)", "0.5"},
	}

	for _, tt := range tests {
		result := testEval(tt.input)
		if isError(result) {
			t.Errorf("error while evaluating %q: %s", tt.input, result.Inspect())
			continue
		}

		if result.Inspect() != tt.expected {
			t.Errorf("wrong result for %q. expected=%q, got=%q", tt.input, tt.expected, result.Inspect())
		}
	}
}

// TestSqrtFunctionErrors tests error cases for the sqrt function
func TestSqrtFunctionErrors(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{"(sqrt)", "sqrt requires exactly 1 argument"},
		{"(sqrt 1 2)", "sqrt requires exactly 1 argument"},
		{"(sqrt #t)", "sqrt expects a number, got BOOLEAN"},
		{"(sqrt \"hello\")", "sqrt expects a number, got STRING"},
	}

	for _, tt := range tests {
		result := testEval(tt.input)
		if !isError(result) {
			t.Errorf("expected error for %q, got %s", tt.input, result.Inspect())
			continue
		}

		if result.Inspect() != "ERROR: "+tt.expected {
			t.Errorf("wrong error message for %q. expected=%q, got=%q", tt.input, tt.expected, result.Inspect()[7:]) // Remove "ERROR: " prefix
		}
	}
}

// TestExptFunction tests the expt function with various inputs
func TestExptFunction(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{"(expt 2 3)", "8"},
		{"(expt 3 2)", "9"},
		{"(expt 2 0)", "1"},
		{"(expt 5 1)", "5"},
		{"(expt 10 2)", "100"},
		{"(expt 2 -1)", "1/2"},
		{"(expt 4 0.5)", "2"},
		{"(expt 4 1/2)", "2"},
		{"(expt 2.5 2)", "6.25"},
		{"(expt 1/2 2)", "1/4"},
	}

	for _, tt := range tests {
		result := testEval(tt.input)
		if isError(result) {
			t.Errorf("error while evaluating %q: %s", tt.input, result.Inspect())
			continue
		}

		if result.Inspect() != tt.expected {
			t.Errorf("wrong result for %q. expected=%q, got=%q", tt.input, tt.expected, result.Inspect())
		}
	}
}

// TestExptFunctionErrors tests error cases for the expt function
func TestExptFunctionErrors(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{"(expt)", "expt requires exactly 2 arguments"},
		{"(expt 2)", "expt requires exactly 2 arguments"},
		{"(expt 2 3 4)", "expt requires exactly 2 arguments"},
		{"(expt #t 2)", "expt expects number as first argument, got BOOLEAN"},
		{"(expt 2 #t)", "expt expects number as second argument, got BOOLEAN"},
	}

	for _, tt := range tests {
		result := testEval(tt.input)
		if !isError(result) {
			t.Errorf("expected error for %q, got %s", tt.input, result.Inspect())
			continue
		}

		if result.Inspect() != "ERROR: "+tt.expected {
			t.Errorf("wrong error message for %q. expected=%q, got=%q", tt.input, tt.expected, result.Inspect()[7:]) // Remove "ERROR: " prefix
		}
	}
}